//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>

#include "cocoa/ccocoafdpoller.h"
#include "cocoa/ccocoaautoreleasepool.h"
#include <buola/io/cfdlistener.h>
#include <buola/algorithm/container.h>
#include <buola/functors/predicates/unary.h>

#include <sys/select.h>
#include <cerrno>

namespace buola { namespace gui { namespace cocoa {

CCocoaFDPoller::CCocoaFDPoller()
{
    FD_ZERO(&mReadFDSet);
    FD_ZERO(&mWriteFDSet);
    FD_ZERO(&mExcFDSet);
    mMaxFD=0;
}

CCocoaFDPoller::CCocoaFDPoller(CEventLoop &pLoop)
    :   io::CFDPoller(pLoop)
{
    FD_ZERO(&mReadFDSet);
    FD_ZERO(&mWriteFDSet);
    FD_ZERO(&mExcFDSet);
    mMaxFD=0;
}

CCocoaFDPoller::~CCocoaFDPoller()
{
}

void CCocoaFDPoller::AddFDListener(io::CFDListener *pListener,int pFD,
        io::ENotify pNotify,void *pClosure,io::ENotifyOp pMode)
{
    lock_guard<mutex> lLock(mFDMutex);

    SFD *lFD=nullptr;

    for(int i=0;i<mFDs.size();i++)
    {
        if(mFDs[i]->mListener==pListener&&mFDs[i]->mFD==pFD&&mFDs[i]->mClosure==pClosure)
        {
            lFD=mFDs[i].get();
            break;
        }
    }

    if(!lFD)
    {
        if(pMode==io::NOTIFYOP_AND||pMode==io::NOTIFYOP_CLEAR) return;
        if(pNotify==io::NOTIFY_NONE) return;

        lFD=&construct_back(mFDs);
        lFD->mListener=pListener;
        lFD->mType=pNotify;
        lFD->mFD=pFD;
        lFD->mClosure=pClosure;
    }
    else
    {
        if(pMode==io::NOTIFYOP_SET)
            lFD->mType=pNotify;
        else if(pMode==io::NOTIFYOP_OR)
            lFD->mType|=pNotify;
        else if(pMode==io::NOTIFYOP_AND)
            lFD->mType&=pNotify;
        else if(pMode==io::NOTIFYOP_CLEAR)
            lFD->mType&=(~pNotify);

        if(lFD->mType==io::NOTIFY_NONE)
            erase_first_if(mFDs,fn::pointer_equal_to(lFD));
    }

    UpdateSets();
}

void CCocoaFDPoller::RemoveFDListeners(int pFD)
{
    lock_guard<mutex> lLock(mFDMutex);

    for(auto i=mFDs.begin();i!=mFDs.end();++i)
    {
        if((*i)->mFD==pFD)
        {
            mFDs.erase(i);
            UpdateSets();
            return;
        }
    }
}

void CCocoaFDPoller::RemoveFDListeners(io::CFDListener *pListener)
{
    lock_guard<mutex> lLock(mFDMutex);

    for(auto i=mFDs.begin();i!=mFDs.end();++i)
    {
        if((*i)->mListener==pListener)
        {
            mFDs.erase(i);
            UpdateSets();
            return;
        }
    }
}

bool CCocoaFDPoller::WaitFDs(const chrono::nanoseconds &pDuration)
{
    //msg_info() << "start poller\n";
    CCocoaAutoReleasePool lPool;
 
//    [self finishLaunching];

    NSEvent *lEvent=[NSApp nextEventMatchingMask:NSAnyEventMask
                           untilDate:[NSDate dateWithTimeIntervalSinceNow:0.05]
                           inMode:NSDefaultRunLoopMode
                           dequeue:YES];
                           
    [NSApp sendEvent:lEvent];
    [NSApp updateWindows];
    
    //msg_info() << "end poller\n";

    return true;
/*
    int lMaxFD;
    fd_set lReadSet;
    fd_set lWriteSet;
    fd_set lExcSet;
    timeval lTime;

    {
        lock_guard<mutex> lLock(mFDMutex);
        
        lReadSet=mReadFDSet;
        lWriteSet=mWriteFDSet;
        lExcSet=mExcFDSet;
        lMaxFD=mMaxFD;
    }

    lTime.tv_sec=pDuration.count()/1000000000;
    lTime.tv_usec=(pDuration.count()%1000000000)/1000;

    if(mEventLoop)
        mEventLoop->GetMutex().unlock();
    int lNumFD=select(lMaxFD+1,&lReadSet,&lWriteSet,&lExcSet,&lTime);
    if(mEventLoop)
        mEventLoop->GetMutex().lock();

    if(lNumFD<0)
    {
        if(errno==EINTR) return false;

        if(errno==EBADF)
        {
            //one of the file descriptors is no longer valid, this shouldn't happen, but just issue a warning
            msg_warn() << "bad file descriptor in select\n";
            return false;
        }

        throw XInternal("error during select"+c_error_string(errno));
    }
    else if(lNumFD>0)
    {
        lock_guard<mutex> lLock(mFDMutex);
        for(int i=0;i<mFDs.size()&&lNumFD>0;i++)
        {
            SFD *lNot=mFDs[i].get();

            io::ENotify lType=io::NOTIFY_NONE;

            if(FD_ISSET(lNot->mFD,&lReadSet))
            {
                lType|=io::NOTIFY_READ;
                lNumFD--;
            }
            if(FD_ISSET(lNot->mFD,&lWriteSet))
            {
                lType|=io::NOTIFY_WRITE;
                lNumFD--;
            }
            if(FD_ISSET(lNot->mFD,&lExcSet))
            {
                lType|=io::NOTIFY_EXCEPTION;
                lNumFD--;
            }

            if(lType!=io::NOTIFY_NONE)
            {
                mFDMutex.unlock();
                lNot->mListener->OnFDListener(lNot->mFD,lType,lNot->mClosure);
                mFDMutex.lock();
            }
        }
        return true;
    }
    return false;
*/
}

void CCocoaFDPoller::UpdateSets()
{
    FD_ZERO(&mReadFDSet);
    FD_ZERO(&mWriteFDSet);
    FD_ZERO(&mExcFDSet);

    mMaxFD=0;

    for(int i=0;i<mFDs.size();i++)
    {
        SFD &lNot=*mFDs[i];
        if(lNot.mType&io::NOTIFY_READ)
        {
            FD_SET(lNot.mFD,&mReadFDSet);
        }
        if(lNot.mType&io::NOTIFY_WRITE)
        {
            FD_SET(lNot.mFD,&mWriteFDSet);
        }
        if(lNot.mType&io::NOTIFY_EXCEPTION)
        {
            FD_SET(lNot.mFD,&mExcFDSet);
        }
        if(lNot.mFD>mMaxFD) mMaxFD=lNot.mFD;
    }
}

/*namespace cocoa*/ } /*namespace gui*/ } /*namespace buola*/ }
